﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace WindowsGame1
{
   
    /// <summary>
    /// Represents UDP client responsible for receiving/sending raw data from/to the Arena Server
    /// </summary>
    public class ArenaClient
    {
        private const int receptionPort = 11000;
        private Arena arena;
        private IPAddress remoteAddress;

        IPEndPoint send_endpoint;
        Socket send_server;

        //Work buffers
        public byte[] send_buffer = new byte[1024];
        public byte[] receive_buffer = new byte[1024];
        //End work buffers

        public ArenaClient(Arena arena, string IP)
        {
            this.arena = arena;
            remoteAddress = IPAddress.Parse(IP);
            send_endpoint = new IPEndPoint(remoteAddress, receptionPort + arena.Me.id);
            send_server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            SubscribeToArena();
            
        }
        public ArenaClient(Arena arena)
        {
            this.arena = arena;
            remoteAddress = IPAddress.Parse("82.192.75.178");
            send_endpoint = new IPEndPoint(remoteAddress, receptionPort + arena.Me.id);
            send_server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            SubscribeToArena();
        }

        public void StartListener()
        {
            bool done = false;
            UdpClient listener = new UdpClient(receptionPort + 1000 + arena.Me.id);
            IPEndPoint RemoteEndPoint = new IPEndPoint(remoteAddress, receptionPort-1);  
            listener.AllowNatTraversal(true);

            //UDP hole punch
            listener.Send(new byte[10], 10, RemoteEndPoint);
            
            //start listening
            try
            {
                while (!done)   
                {
                    Thread.Sleep(15);
                    this.receive_buffer = listener.Receive(ref RemoteEndPoint);
                    //System.Console.WriteLine("Received packet");
                   
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            finally
            {
                listener.Close();
            }
        }

        public void StartSender()
        {
            preparePacket();
            SubscribeToArena();
            bool done = false;

            IPEndPoint RemoteEndPoint = new IPEndPoint(remoteAddress, receptionPort + arena.Me.id);
            Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            try
            {
                int delta = 0;
                int start = DateTime.Now.Millisecond;
                while (!done)
                {
                    if (delta >= 5)
                    {
                        preparePacket();
                        server.SendTo(send_buffer, send_buffer.Length, SocketFlags.None, RemoteEndPoint);
                        //System.Console.WriteLine("Sent packet");
                        //Thread.Sleep(15);
                    }
                    int end = DateTime.Now.Millisecond;
                    delta = end - start;
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            finally
            {
                server.Close();
            }
        }
        public void SendMyState()
        {
            
            //prepare my state
            int id = arena.Me.id;
            float myX, myY, myRotation;
            myX = arena.Me.Position.X;
            myY = arena.Me.Position.Y;
            myRotation = arena.Me.Rotation;
            int bullet_count = arena.ListOfMyBullets.Count;
            List<float> coordinates = new List<float>();
            coordinates.Add(myX);
            coordinates.Add(myY);
            coordinates.Add(myRotation);

            for (int i = 0; i < 16; i++)
            {
                if (i < bullet_count)
                {
                    if (!arena.ListOfMyBullets[i].isSent)
                    {
                        coordinates.Add(arena.ListOfMyBullets[i].Position.X);
                        coordinates.Add(arena.ListOfMyBullets[i].Position.Y);
                        coordinates.Add(arena.ListOfMyBullets[i].Rotation);
                        arena.ListOfMyBullets[i].isSent = true;
                    }
                }
                else
                {
                    coordinates.Add(0);
                    coordinates.Add(0);
                    coordinates.Add(0);
                }

            }
            send_buffer = new byte[sizeof(int) + coordinates.Count * sizeof(float)];
            //Write id
            byte[] converted_int = BitConverter.GetBytes(id);
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(converted_int);
            }
            for (int j = 0; j < sizeof(int); ++j)
            {
                send_buffer[j] = converted_int[j];
            }

            //Write floats
            for (int i = 0; i < coordinates.Count; ++i)
            {
                byte[] converted = BitConverter.GetBytes(coordinates[i]);
                if (BitConverter.IsLittleEndian)
                    Array.Reverse(converted);
                for (int j = 0; j < sizeof(float); ++j)
                {
                    send_buffer[sizeof(int) + i * sizeof(float) + j] = converted[j];
                }
            }

            //send
            try
            {
                send_server.SendTo(send_buffer, send_buffer.Length, SocketFlags.None, send_endpoint);

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            finally
            {
                //send_server.Close();
            }
        }
        public void SubscribeToArena()
        {
            IPEndPoint RemoteEndPoint = new IPEndPoint(remoteAddress, receptionPort);
            Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            try
            {
                server.SendTo(arena.GetMyStatePacket(), arena.GetMyStatePacket().Length, SocketFlags.None, RemoteEndPoint);
                System.Console.WriteLine("Tried to subscribe");
                Thread.Sleep(15);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            finally
            {
                server.Close();
            }
        }

        private void preparePacket()
        {
            this.send_buffer = this.arena.GetMyStatePacket();
        }


        internal void SetArenaIp(string p)
        {
            this.remoteAddress = IPAddress.Parse(p);
        }
    }
}
